package com.nxy.cpfsfe.busi.bbatproc.chka;

import com.adtec.starring.exception.BaseException;
import com.nxy.cpfsfe.enumtype.RespCd;
import com.nxy.cpfsfe.mybatis.mapper.TePChlSettMapper;
import com.nxy.cpfsfe.mybatis.mapper.TrChlBizTypMapper;
import com.nxy.cpfsfe.mybatis.pojo.TePChlSett;
import com.nxy.cpfsfe.mybatis.pojo.TrChlBizTyp;
import com.nxy.cpfsfe.util.DateUtil;
import com.nxy.cpfsfe.util.zookeeper.DistributedLock;
import com.nxy.cpfsfe.util.zookeeper.ZookeeperLockUtil;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

/**
 * 
 * 抽象对账类
 * 
 * @author jjw
 *
 */
public abstract class AbsChkASrv {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(AbsChkASrv.class);
	
	protected String orgId;
	
	protected String pCId;
	
	protected Date chkAD;
	
	protected String mfeTyp;
	
	protected DistributedLock distributedLock = null;
	
	protected SqlSession sqlSession;
	
	protected TePChlSett tePChlSett;
	
	protected String step;
	
	/**
	 * 
	 * Description:对账主流程
	 *
	 * @author jjw
	 * @data 2019年9月6日
	 */
	public void chkAProcess(){
		
		try{
			LOGGER.info("开始进行机构[{}]与支付通道[{}]的对账流程，对账日期[{}]",orgId,pCId,DateUtil.date3Str(chkAD));
			canChkA();
			chkStep();
			chkData();
			chkAByStep();
			LOGGER.info("机构[{}]与[{}]通道[{}]日对账完成",orgId,pCId,DateUtil.date3Str(chkAD));
		}catch(BaseException e){
			if(e.getErrorCode().equals(RespCd.NCPS9408)){
				LOGGER.info(e.getErrorDesc());
			}else if(e.getErrorCode().equals(RespCd.NCPO0001)){
				throw e;
			}else{
				updateChkASchedule(null,"2",null,e.getErrorDesc());
				throw e;
			}
		}catch(Throwable e){
			String errMsg = String.format("机构[%s]与[%s]通道[%s]日对账失败，出现未知异常[%s]", orgId,pCId,DateUtil.date3Str(chkAD),e.getMessage());
			updateChkASchedule(null,"2",null,errMsg);
			throw new BaseException(RespCd.NCPO6037,e,errMsg);
		}finally{
			if(distributedLock != null){
				distributedLock.unlock();
			}
		}
		
	}

	/**
	 *
	 * Description:检查是否有线程正在做该机构的对账流程
	 *
	 * @author jjw
	 * @data 2019年9月6日
	 */
	private void canChkA(){

		distributedLock = ZookeeperLockUtil.getInstance().creaZooKeeperLock(orgId + "_" + pCId + "_" + DateUtil.date4Str(chkAD));

		if(!distributedLock.tryLock()){
			String errMsg = String.format("机构[%s]正在进行与[%s]通道[%s]日对账，请勿重复操作", orgId,pCId,DateUtil.date3Str(chkAD));
			throw new BaseException(RespCd.NCPO0001,errMsg);
		}

	}

	/**
	 *
	 * Description:检查该机构该通道是否已进行过对账流程
	 *
	 * @author jjw
	 * @data 2019年9月6日
	 */
	private void chkStep(){

		//查询支付通道对账记录表，检查该机构的该通道该对账日期是否存在对账记录
		TePChlSettMapper tePChlSettMapper = sqlSession.getMapper(TePChlSettMapper.class);
		TePChlSett tePChlSettQR = tePChlSettMapper.queryOrgChlChkA(tePChlSett);

		if(tePChlSettQR == null){
			tePChlSett.setMfeTyp(mfeTyp);
			tePChlSett.setStep("0");
			tePChlSett.setChkASta("0");
			tePChlSett.setChkABgnTm(new Date());
			tePChlSettMapper.insertOrgChlChkA(tePChlSett);
			step = tePChlSett.getStep();
		}else{
			if("1".equals(tePChlSettQR.getChkASta())){
				String errMsg = String.format("机构[%s]与[%s]通道[%s]日对账已完毕", orgId,pCId,DateUtil.date3Str(chkAD));
				throw new BaseException(RespCd.NCPS9408,errMsg);
			}else{
				updateChkASchedule(null,"0",null,"无");
			}

			step = tePChlSettQR.getStep();
		}

	}
	
	/**
	 * 
	 * Description:检查数据的正确性
	 *
	 * @author jjw
	 * @data 2019年9月6日
	 */
	private void chkData(){
		
		if(chkAD == null){
			String errMsg = String.format("对账日期为空，无法进行机构[%s]与[%s]通道对账", orgId,pCId);
			throw new BaseException(RespCd.NCPO1019,errMsg);
		}
		
		//查询机构通道业务种类表，检查机构是否开通了对应通道
		TrChlBizTypMapper trChlBizTypMapper = sqlSession.getMapper(TrChlBizTypMapper.class);
		TrChlBizTyp trChlBizTypQC = new TrChlBizTyp();
		trChlBizTypQC.setOrgId(orgId);
		trChlBizTypQC.setpCId(pCId);
		TrChlBizTyp trChlBizTypQR = trChlBizTypMapper.queryOpenOrgChl(trChlBizTypQC);
		
		if(trChlBizTypQR == null){
			String errMsg = String.format("机构[%s]未开通[%s]通道，不能进行[%s]日该通道对账", orgId,pCId,DateUtil.date3Str(chkAD));
			throw new BaseException(RespCd.NCPO6068,errMsg);
		}
		
	}
	
	/**
	 * 
	 * Description:根据已完成对账的步骤，开始未完成的对账步骤
	 *
	 * @author jjw
	 * @data 2019年9月6日
	 */
	private void chkAByStep(){
		
		long start = 0;
		long end = 0;

		//文件下载与入库应该在一个步骤中完成，不然容器更换后，下载好的文件就不存在了，入库肯定会失败
		switch(step){
			case "0":
				start = System.currentTimeMillis();
				dlChkAF();
				end = System.currentTimeMillis();
				LOGGER.info("机构[{}]与[{}]通道[{}]日的对账文件下载解压耗时：[{}]ms",orgId,pCId,DateUtil.date3Str(chkAD),end - start);
				start = System.currentTimeMillis();
				chkAFStrgDb();
				end = System.currentTimeMillis();
				LOGGER.info("机构[{}]与[{}]通道[{}]日的对账文件入库耗时：[{}]ms",orgId,pCId,DateUtil.date3Str(chkAD),end - start);
			case "1":
				start = System.currentTimeMillis();
				updateTbaSta();
				end = System.currentTimeMillis();
				LOGGER.info("机构[{}]与[{}]通道[{}]日的更新账务流水耗时：[{}]ms",orgId,pCId,DateUtil.date3Str(chkAD),end - start);
			case "2":
				start = System.currentTimeMillis();
				updatePSta();
				end = System.currentTimeMillis();
				LOGGER.info("机构[{}]与[{}]通道[{}]日的更新支付流水耗时：[{}]ms",orgId,pCId,DateUtil.date3Str(chkAD),end - start);
		}
		
	}
	
	/**
	 * 
	 * Description:更新对账进度
	 *
	 * @author jjw
	 * @data 2019年9月24日
	 */
	protected void updateChkASchedule(String step,String chkASta,Date chkAFinTm,String errMsg){
		
		TePChlSettMapper tePChlSettMapper = sqlSession.getMapper(TePChlSettMapper.class);
		tePChlSett.setStep(step);
		tePChlSett.setChkASta(chkASta);
		tePChlSett.setChkAFinTm(chkAFinTm);
		tePChlSett.setErrMsg(errMsg);
		tePChlSettMapper.updateOrgChlChkA(tePChlSett);
		
	}
	
	/**
	 * 
	 * Description:下载对账文件
	 *
	 * @author jjw
	 * @data 2019年9月6日
	 */
	protected abstract void dlChkAF();
	
	/**
	 * 
	 * Description:对账文件入库
	 *
	 * @author jjw
	 * @data 2019年9月7日
	 */
	protected abstract void chkAFStrgDb();
	
	/**
	 * 
	 * Description:更新账务流水表
	 *
	 * @author jjw
	 * @data 2019年9月7日
	 */
	protected abstract void updateTbaSta();
	
	/**
	 * 
	 * Description:更新支付流水表
	 *
	 * @author jjw
	 * @data 2019年9月9日
	 */
	protected abstract void updatePSta();
	
	/**
	 * 
	 * Description:删除临时文件
	 *
	 * @author jjw
	 * @data 2019年9月24日
	 */
	protected abstract void delTmpF();

}
